-module(master).

-include("db.hrl").

-compile(export_all).

start_all() ->
    start_args(length(nodes())).

start_args(Amount) ->
    [First|Others] = get_random_nodes(Amount),
    rpc:call(First, node, start, []),
    rpc:multicall(Others, node, start_join, [First]).

start_test_mesh(Amount) ->
    Env = [{network,mesh}, {chordsetup,undefined}, {chordlookup,undefined}, {backend,ets},
           {verbose,false}, {redundancy,0}, {hashsize,160}, {stabilize_interval,10000}, {fix_fingers_interval,10000}, 
           {visualNode,undefined}],
    start_random_args(Amount, Env, false, 5000).

start_test_chord_delay(Amount) ->
    [First|Others] = get_random_nodes(Amount),
    Env = [{network,chord}, {chordsetup,aggressive}, {chordlookup,iterative}, {backend,ets},
           {verbose,false}, {redundancy,0}, {hashsize,160}, {stabilize_interval,10000}, {fix_fingers_interval,10000}, 
           {visualNode,undefined}],
    start_first_node(First, Env),
    start_joining_nodes_delay(First, Others, true, 5000, Env).

start_random_args(Amount, Env, UseJoinDelay, JoinDelay) ->
    [First|Others] = get_random_nodes(Amount),
    start_first_node(First, Env),
    start_joining_nodes_func(First, Others, Env, UseJoinDelay, JoinDelay).

% Start the nodes selected from gui. If some node is already running
% a database, join that network, otherwise start a new.
start_selected_args(Nodes, Env, UseJoinDelay, JoinDelay) ->
    case get_random_db_node() of
        {error,_} -> [First|Others] = Nodes,
                     start_first_node(First, Env),
                     start_joining_nodes_func(First, Others, Env, UseJoinDelay, JoinDelay);
        Node -> start_joining_nodes_func(Node, Nodes, Env, UseJoinDelay, JoinDelay)
    end.

start_first_node(Node,Env) ->
    rpc:call(Node, application, load, [db]),
    [ rpc:call(Node, application, set_env, [db, Key, Value]) || {Key, Value} <- Env ],
    rpc:call(Node, node, start, []).

start_joining_nodes_func(Target, Nodes, Env, UseJoinDelay, JoinDelay) ->
    case UseJoinDelay of
      true -> start_joining_nodes_delay(Target, Nodes, Env, UseJoinDelay, JoinDelay);
      false -> start_joining_nodes(Target, Nodes, Env, UseJoinDelay, JoinDelay)
    end.

start_joining_nodes(Target, Nodes, Env, false, _JoinDelay) ->
    rpc:multicall(Nodes, application, load, [db]),
    [ rpc:multicall(Nodes, application, set_env, [db, Key, Value]) || {Key, Value} <- Env ],
    rpc:multicall(Nodes, node, start_join, [Target]).

start_joining_node(Target, Node, Env) ->
    Res = rpc:call(Node, application, load, [db]),
    io:format("Received rpc:call: ~w~n", [Res]),
    [ rpc:call(Node, application, set_env, [db, Key, Value]) || {Key, Value} <- Env ],
    rpc:call(Node, node, start_join, [Target]).

start_joining_nodes_delay(Target, Nodes, Env, true, JoinDelay) ->
    lists:foreach(fun(X) ->
                    timer:sleep(JoinDelay),
                    start_joining_node(Target,X,Env)
                    end, Nodes).

%check_arguments()
%    case ((Backend /= ets)  and (Backend /= dict)) or
%         ((Network /= mesh)  and (Network /= chord)) or
%         ((Verbose /= true) and (Verbose /= false)) or
%         not (is_integer(Redundancy)) or (Amount < 1)of


stop_all() ->
    rpc:multicall(all_nodes(), application, stop, [db]).

stop_selected(Nodes) ->
    rpc:multicall(Nodes, node, stop, []).
        

shutdown_remote_nodes() ->
    rpc:multicall(nodes(), erlang, halt, []).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%
%%% Functions to find random Erlang nodes
%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

get_random_nodes(Amount) ->
    Nodes = nodes(), % Don't include own node
    case Amount > length(Nodes) of 
        true  -> Nodes;
        false ->
            lists:foldl(fun(_, Acc) ->
                        [get_random_unique(Nodes, Acc)]
                            ++ Acc
                end, [], lists:seq(1, Amount))
    end.


get_random_unique(Nodes, CurrentList) ->
    <<A:32, B:32, C:32>> = crypto:strong_rand_bytes(12),
    random:seed(A,B,C),
    Node = lists:nth(random:uniform(length(Nodes)), Nodes),
    case lists:member(Node, CurrentList) of
        true  -> get_random_unique(Nodes, CurrentList);
        false -> Node
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%
%%% Helper functions to find db nodes
%%%
%%% (Same code as in test_lib, copy paste!)
%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Returns a list of all connected 
% erlang nodes, including itself
all_nodes() ->
    [node()] ++ nodes().

% Returns a list of all nodes currently 
% running a database instance
all_db_nodes() ->
    [Node || Node <- all_nodes(), is_running_db(Node)].

% Returns a random database node
get_random_db_node() ->
    <<A:32, B:32, C:32>> = crypto:strong_rand_bytes(12),
    random:seed(A,B,C),
    DB_Nodes = all_db_nodes(),
    case length(DB_Nodes) > 0 of
        true  -> lists:nth(random:uniform(length(DB_Nodes)), DB_Nodes);
        false -> {error,not_running}
    end.

% Check if node is running the database application,
% returns boolean()
is_running_db(Node) ->
    case rpc:call(Node,application,which_applications,[]) of
        {badrpc,_}   -> false;
        Running_apps -> lists:keymember(db,1,Running_apps)
    end.
